// Compiler负责解析指令和差值表达式
class Compiler {
  constructor(vm) {
    this.el = vm.$el
    this.vm = vm
    // 立刻执行节点的处理
    this.compile(this.el)
  }
  // 编译模版，处理文本节点和元素节点
  compile (el) {
    let childNodes = el.childNodes
    // 遍历所有的节点，判断是什么类型的节点，做不同的节点处理
    Array.from(childNodes).forEach(node => {
      if (this.isTextNode(node)) {
        // 处理文本节点
        this.compileText(node)
      } else if (this.isElementNode(node)) {
        // 处理元素节点
        this.compileElement(node)
      }

      // 如果节点存在子节点，则需要递归处理子节点
      if (node.childNodes && node.childNodes.length) {
        this.compile(node)
      }
    })
  }
  // 遍历元素节点，处理指令
  compileElement (node) {
    /**
     * 1. 通过node.attribute可以获取到元素节点的所有属性的列表，该列表是一个伪数组
     * 2. 将属性的伪数组转换为真数组，然后遍历数组并查找数组中属于指令的属性
     * 3. 处理具有指令属性的节点
     */
    Array.from(node.attributes).forEach(attr => {
      // 判断是否是属性
      let attrName = attr.name

      if (this.isDirective(attrName)) {
        // 属性名
        attrName = attrName.startsWith('@') ? attrName : attrName.substr(2)
        // 获取属性值，属性值就为vue实例绑定值的key
        let key = attr.value
        this.update(node, key, attrName)
      }
    })
  }

  // 根据节点的属性类型处理不同的指令（v-text|v-module|v-html|v-on）
  update (node, key, attrName) {
    /**
     * 此处 updateFn 被调用的时候并不是在 Compiler 中调用的而是在 update 中调用的
     * 因此此处的 this 的指向指向的并不是 Compiler 而是 update
     * 因此我们需要将 this 指向 Compiler 就要使用到 call 或者 apply
     **/
    let updateFn = attrName.startsWith('@') || attrName.startsWith('on') ? this.onUpdater : this[`${attrName}Updater`]
    updateFn && updateFn.call(this, node, this.vm[key], key, attrName)
  }

  // 处理v-text指令
  textUpdater (node, value, key) {
    node.textContent = value
    new Watcher(this.vm, key, (newValue) => {
      node.textContent = newValue
    })
  }

  // 处理v-model指令
  modelUpdater (node, value, key) {
    node.value = value
    new Watcher(this.vm, key, (newValue) => {
      node.value = newValue
    })
    // 双向绑定
    node.addEventListener('input', () => {
      this.vm[key] = node.value
    })
  }

  // 处理v-html指令
  htmlUpdater (node, value, key) {
    node.innerHTML = value
    new Watcher(this.vm, key, (newValue) => {
      if (!newValue) return
      node.innerHTML = newValue
    })
  }

  // 处理v-on指令
  onUpdater (node, value, key, attrName) {
    // v-on指令获取事件名称开始的位置
    const eventIdx = attrName.indexOf(':') > 0 ? attrName.indexOf(':') + 1 : attrName.indexOf('@') + 1
    // v-on指令事件参数 .stop 等
    const eventPropIdx = attrName.indexOf('.') + 1
    // 方法名
    const event = eventIdx > 0 ? (eventPropIdx ? attrName.substr(eventIdx, eventPropIdx - eventIdx - 1) : attrName.substr(eventIdx)) : null
    // 回调方法
    const cb_start = key.indexOf('\(')
    const cb_end = key.lastIndexOf('\)')
    const cb = key.indexOf('=>') > -1 ? key : (cb_start > 0 ? key.substr(0, cb_start) : key)
    // 回调方法参数
    const cb_prams = cb_start > -1 ? key.substr(cb_start + 1, cb_end - cb_start - 1) : null
    const _this = this
    if (!event) return
    node.addEventListener(event, function () {
      const methods = _this.vm.$options.methods || {}
      try {
        methods[cb] && methods[cb](cb_prams)
        console.log(_this)
      } catch (error) {
        console.log(error)
      }
    })
  }

  // 遍历文本节点，处理差值表达式
  compileText (node) {
    // 文本节点需要先判断该文本节点是否有差值表达式
    let reg = /\{\{(.+?)\}\}/
    let value = node.nodeValue
    if (reg.test(value)) {
      /**
       * 如果是有差值表达式则将文本节点的内容中差值表达式的部分用对应的值替换
       * 使用RegExp.$1.trim()可以找出差值表达式中间的内容，该内容就为vue实例中绑定值的key
       * 将差值表达式部分的内容替换成响应式的value值
       */
      let key = RegExp.$1.trim()
      node.nodeValue = value.replace(reg, this.vm[key])

      // 创建watcher对象，当数据改变更新视图
      new Watcher(this.vm, key, (newValue) => {
        node.textContent = newValue
      })
    }
  }
  // 判断元素属性是否是指令
  isDirective (attrName) {
    return attrName.startsWith('v-') || attrName.startsWith('@')
  }
  // 判断节点是否是文本节点
  isTextNode (node) {
    return node.nodeType === 3
  }
  // 判断节点是否是元素节点
  isElementNode (node) {
    return node.nodeType === 1
  }
}